<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXOutput
 *
 * GXPage uses GXOutput to build the html header and to send the actual data back to GXPage.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Engine
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		SingletonPattern
 */

class GXOutput extends SingletonPattern {

	// mime object
	public $mime;

	// default output format
	private $Output = 'HTML';
	// standard header array
	private static $OutputHeader;
	// filename, which can be set by any script
	private static $FileName;

	/**
	 * getInstance
	 *
     * Returns the single unique instance of this class.
	 * @return object unique instance of this object
     */
	public static function getInstance(){
		return SingletonPattern::getSingleton( __CLASS__ );
	}

	function __construct(){
		return true;
	}

	function setOutput( $out = 'HTML' ){
		$this->Output = $out;
	}

	function setFileName( $name = null ){
		self::$FileName = $name;
		$this->Output = 'File';
		return self::$FileName;
	}

	function getXMLString( $source = null ){
		switch( get_class($source) ){
			case 'GXDOM':
				$xml = $source->exec->xml();
				break;
			case 'DOM':
			case 'DOMDocument':
				$xml = $source->saveXML();
				break;
			case 'SimpleXML':
				$xml = $source->asXML();
				break;
			default:
				trigger_error( 'Unsupported object type', E_USER_ERROR );
				return false;
		}
		return $xml;
	}

	function process( $output = null, $source = null, $sheet = null ){
		$this->mime = new GXDOM( dirname(__FILE__) .DIRECTORY_SEPARATOR .'Data' .DIRECTORY_SEPARATOR .'mime.xml' );
		// Set default output header values
		self::$OutputHeader = array(
			'Content-Type'		=> $this->mime->exec->xpath('/mime/*[ @code = "html" ]',0)->nodeValue,
			'Cache-Control'		=> 'no-cache',
			'Pragma'			=> 'no-cache',
			'Content-Language'	=> GXPage::get('Params')->getLanguage()
			);

		// Default output...it might come in as NULL
		if( !$output ){
			$output = $this->Output;
		}
		// if we transforming and we have no stylesheet, stop
		if( $output != 'XML' && $output != 'RAW' && !$sheet ){
			trigger_error( 'Missing StyleSheet; cannot continue', E_USER_ERROR );
			return false;
		}

		// if we're outputting XML and the current browser can't do it...
		if( $output == 'XML' && !$this->canXSL() ){
			$output = 'HTML';
		}

		// init xml var
		$xml = null;
		$result = null;

		// Now handle output
		switch( $output ){

			case 'HTML':
			case 'RAWHTML':
				if( !($result = $this->genHTML( $source, $sheet )) ){
					return $this->process( 'XML', $source );
				}
				// if we want rawhtml, get rid of the beginning doctype, since it interferes with javascript parsing
				if( $output == 'RAWHTML' && substr($result,0,9) == '<!DOCTYPE' ){
					$result = preg_replace('/<!DOCTYPE.*?.dtd">/','',$result);
				}
				self::$OutputHeader['Content-Length'] = strlen($result);
				break;

			case 'RAW':
				$result = $source;
				// we're expecting a string; if it's not, try to get one
				if( gettype($result) != 'string' ){
					if( !($result = $this->getXMLString($result)) ){
						trigger_error( 'Output requested RAW but received ' .gettype($source) .' and unable to convert to string', E_USER_ERROR );
						$result = 'ERROR';
					}
				}
				self::$OutputHeader['Content-Length'] = strlen($result);
				break;

			case 'XML':
			case 'RAWXML':
				if( is_object($source) ){
					$xml = $this->getXMLString($source);
				}
				$result = $xml;
				self::$OutputHeader['Content-Type'] = $this->mime->exec->xpath('/mime/*[ @code = "xml" ]',0)->nodeValue;
				self::$OutputHeader['Content-Length'] = strlen($result);
				break;

			case 'File':
				$result = $source;
				$fileName = self::$FileName;
				if( !$fileName ){
					$fileName = 'GXPage-' .date('Ymd') .'txt';
				}
				$ext = substr($fileName, strrpos($fileName, '.') + 1);
				if( $mime = $this->mime->get('/mime/*[ @code = "' .strtolower($ext) .'" ]','value') ){
					self::$OutputHeader['Content-Type'] = $mime;
				}

				// Sanitize the filename...make it safe for OS digestion
				$tmp = ereg_replace("\.[\.]+", "", $fileName);
				$tmp = ereg_replace("^[\/]+", "", $tmp);
				$tmp = ereg_replace("^[a-zA-Z][:\|][\/]?", "", $tmp);
				$fileName = $tmp;

				// When downloading a file, be careful of settings for Expires.
				// IE will save the file if expires is set to now, but will not allow the user to automatically open it.
				// It appears that IE respects the Expires setting so rigidly that once the file is downloaded, the cache deletes it before it can be opened.
				self::$OutputHeader['Pragma']						= 'cache';
				self::$OutputHeader['Cache-Control']				= 'public, must-revalidate, max-age=0';
				self::$OutputHeader['Connection']					= 'close';
				self::$OutputHeader['Expires']						= date("r", time() + (60 * 5));
				self::$OutputHeader['Last-Modified']				= date("r", time());
				self::$OutputHeader['Content-Disposition']			= 'attachment; filename="' .$fileName .'"';
				self::$OutputHeader['Content-Transfer-Encoding']	= 'binary';
				self::$OutputHeader['Content-Length'] 				= strlen($result);
				break;

			default:
				trigger_error( 'Output value incorrect: ' .$this->Output, E_USER_ERROR );
				return false;

		}

		// Handle the header
		if( !headers_sent() ){
			$this->setHeader();
		}
		return $result;
	}

	function genHTML( $xml = null, $sheet = null ){
		trigger_error( 'About to transform to html', E_USER_WARNING );

		$this->GXXSL = GXXSL::getInstance();
		$this->GXXSL->setEncoding( GXPage::get('Run')->Encoding );

		if( !($result = $this->GXXSL->process( $xml, $sheet )) ){
			trigger_error( 'Unable to transform with ' .$sheet .'; check your web server error log for more detailed information, or set Debug to 1 in SystemConfig', E_USER_ERROR );
			trigger_error( 'System error on XSL transformation; cannot continue.', E_USER_NOTICE );
			return false;
		}
		return $result;
	}

	function setHeader(){
		if( !count(self::$OutputHeader) ){
			trigger_error( 'No output header items', E_USER_ERROR );
			return false;
		}
		// update Content-Type to include encoding
		$encoding = GXPage::get('Run')->Encoding;
		self::$OutputHeader['Content-Type'] = self::$OutputHeader['Content-Type'] .';charset=' .$encoding;
		foreach( self::$OutputHeader as $name => $val ){
			header( $name .': ' .$val );
		}
		return true;
	}

	/* canXSL
	 *
     * Determines if the current browser supports client-side XML transformation.
	 *
	 * @returns Boolean
     */
	function canXSL(){
		// create variable to store this browser's profile
		$Profile = null;
		preg_match_all('/(microsoft internet explorer|msie|netscape6|netscape|galeon|phoenix|mozilla firebird|firebird|firefox|chimera|camino|epiphany|safari|k-meleon|mozilla|opera|konqueror|icab|lynx|links|ncsa mosaic|amaya|omniweb|hotjava|browsex|amigavoyager|amiga-aweb|ibrowse)[\/\sa-z(]*([0-9]+)([\.0-9a-z]+)?/i',
					   $_SERVER['HTTP_USER_AGENT'],
					   $Profile);

		switch(true){
			// Opera
			case $Profile[1][0] == 'Opera':
				trigger_error( 'Browser is Opera (' .$_SERVER['HTTP_USER_AGENT'] .')', E_USER_WARNING );
				$XSLOK=false;
				break;
			// IE 6 and up
			case $Profile[1][0] == 'Mozilla' && $Profile[1][1] == 'MSIE' && $Profile[2][1] >= 6:
				trigger_error( 'Browser is IE 6 or greater (' .$_SERVER['HTTP_USER_AGENT'] .')', E_USER_WARNING );
				$XSLOK=true;
				break;
			// Mozilla
			case $Profile[1][0] == 'Mozilla' && !$Profile[1][1]:
				trigger_error( 'Browser is Mozilla (' .$_SERVER['HTTP_USER_AGENT'] .')', E_USER_WARNING );
				$XSLOK=true;
				break;
			// Netscape(s) based on Mozilla
			case $Profile[1][0] == 'Mozilla' && ($Profile[1][1] == 'Firefox' or $Profile[1][1] == "Netscape"):
				trigger_error( 'Browser is based on Gecko (' .$_SERVER['HTTP_USER_AGENT'] .')', E_USER_WARNING );
				$XSLOK=true;
				break;
			// Presumed to not support xsl
			default:
				trigger_error( 'Browser is not known to support XSL transformation (' .$_SERVER['HTTP_USER_AGENT'] .')', E_USER_WARNING );
				$XSLOK=false;
		}
		$this->XSLOK = $XSLOK;
		$this->Browser = $_SERVER['HTTP_USER_AGENT'];
		return $XSLOK;
	}

}

?>
